feat: WebAuthn/passkey support on Linux via electron-webauthn-linux#2337
feat: WebAuthn/passkey support on Linux via electron-webauthn-linux#2337phelix001 wants to merge 9 commits intoferdium:developfrom
Conversation
Webview sessions previously had no setPermissionRequestHandler, causing all permissions to be auto-granted by Electron's default behavior. This adds an explicit allowlist for safe permissions (media, notifications, clipboard, etc.) and enables HID/USB/Serial permission checks needed for hardware FIDO2 security key detection. Unknown permissions are denied and logged for debugging. Related: ferdium#1487, ferdium#2316 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds WebAuthn/passkey support for service webviews on Linux by wiring in the electron-webauthn-linux package. Registers IPC handlers in the main process, exposes the WebAuthn bridge via contextBridge in the preload, and injects the navigator.credentials monkey-patch page script. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…exist - Only inject webauthnPageScript on dom-ready when hasStoredCredentials() returns true for the current hostname, preventing Google from showing dead-end passkey prompts when the credential store is empty - Move WebAuthn page script injection from preload (always) to main process dom-ready handler (conditional) - Add hasCredentials IPC bridge to preload for page-level credential checks - Guard electronWebAuthn contextBridge behind process.platform === 'linux' - Add error logging to ErrorBoundary componentDidCatch - Fix AppStore._readSandboxes to handle non-array JSON data Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move CDP-based WebAuthn page script injection from webview-only to all webContents types, enabling passkey support in popup BrowserWindows (e.g. Google passkey settings page). Key changes: - Add Page.enable for script persistence across navigations - Add Runtime.addBinding CDP bridge for popup windows without preloads - Route CDP bridge calls to AuthenticatorManager for create/get/hasCredentials Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Integrates Linux-native WebAuthn/passkey support into Ferdium by wiring electron-webauthn-linux into the main process and webview/popup contexts, aiming to make navigator.credentials.create/get functional on Linux where Electron’s built-in WebAuthn is stubbed.
Changes:
- Add
electron-webauthn-linuxdependency and initialize WebAuthn support on Linux at app startup. - Inject a WebAuthn monkey-patch into all
webContentsvia Chrome DevTools Protocol (CDP), including a CDP binding bridge for popups without preloads. - Add a Linux-only IPC bridge in the webview preload and harden sandbox JSON parsing.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/index.ts | Initializes WebAuthn on Linux, injects WebAuthn patching via CDP, adds popup bridging, and updates permission/window-open handling for webviews. |
| src/webview/recipe.ts | Exposes a Linux-only window.electronWebAuthn IPC bridge from the webview preload. |
| src/stores/AppStore.ts | Makes sandbox config parsing resilient to non-array JSON content. |
| src/components/util/ErrorBoundary/index.tsx | Adds error logging details in componentDidCatch. |
| package.json | Adds electron-webauthn-linux dependency. |
| pnpm-lock.yaml | Locks the new dependency and its resolution metadata. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)
src/index.ts:450
- The injected
dom-readydiagnostics (executing arbitrary JS and loggingDIAG ...) will ship in production and run on matching URLs. This adds noise, can leak page state into logs, and increases risk by injecting extra JS into third-party pages. Please remove this block or gate it behindisDevMode/ an explicit debug flag.
// Diagnostic: check WebAuthn API state after page loads
contents.on('dom-ready', () => {
const url = contents.getURL();
if (url.includes('google.com') || url.includes('myaccount')) {
contents
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Remove production diagnostic block that injected JS into Google pages
and logged page state on every dom-ready. Debug-only code, should
never have shipped.
- Replace naive rootDomain (.split('.').slice(-2)) with tldts.getDomain()
for correct eTLD+1 matching in the popup setWindowOpenHandler. The old
heuristic treated evil.co.uk and google.co.uk as the same domain.
- Fix misleading comment on permission handlers to accurately describe
that setPermissionRequestHandler gates grants while
setPermissionCheckHandler allows hid/serial/usb feature detection
(actual device access goes through select-*-device session events).
Re: Copilot's other two comments --
"Permission handler too broad": Ferdium is a messaging service
aggregator. Services need media (voice/video), notifications (message
alerts), display-capture (screen sharing), clipboard, and fullscreen to
function. This is the minimum viable set, not an over-grant. Prompting
per-service/per-origin would break core UX for every user to defend
against a threat model that doesn't apply -- these are user-chosen
services running in isolated webview partitions, not untrusted iframes.
"Add hid/usb/serial to setPermissionRequestHandler": That's not how
Electron works. HID, USB, and Serial device access is gated by
select-hid-device / select-usb-device / select-serial-port session
events, not setPermissionRequestHandler. Adding them there does nothing.
The setPermissionCheckHandler correctly includes them so that
navigator.hid/usb/serial feature detection returns true. Copilot
confused the check handler (feature detection) with the request handler
(permission grants).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@SpecialAro Almost -- the code is functionally complete and all checks pass. One remaining pre-merge item: the Addressed all of Copilot's review comments in b1db549:
The other two Copilot comments (permission set "too broad" and adding hid/usb/serial to the request handler) were incorrect -- explained in the thread replies. |
…x@0.1.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CDP debugger.attach() on every webContents triggered Google's automation detection, causing "Couldn't sign you in - This browser or app may not be secure" on all Google services. Replace the entire CDP approach with preload-based injection: - recipe.ts: inject page script at document-start via DOM <script> element, which runs in the main world before page scripts - New webauthn-popup-preload.ts: same pattern for popup windows that don't get the recipe preload - setWindowOpenHandler passes popup preload via overrideBrowserWindowOptions on Linux This removes ~190 lines of CDP code (debugger.attach, Page.addScriptToEvaluateOnNewDocument, Runtime.addBinding, the entire CDP bridge handler) and eliminates all automation signals that Google detects. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@phelix001 , can you make the electron-webauthn-linux public? Thanks |
Picks up fixes for missing hasCredentials in contextBridge and webview preload bridge setup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Bumped to electron-webauthn-linux@0.1.1 — fixes contextBridge bridge setup and hasCredentials IPC binding. Package is live on npm: https://www.npmjs.com/package/electron-webauthn-linux |
|
I will have a look. Probably add a README.md and LICENSE files, etc. Just tell claude to tidy it up a bit ;) |
|
Hi @phelix001 , at the moment, I don't think this would work for our project (teams-for-linux). But I might explore using some of the ideas, and the libraries you use. If that is the case, I will put the right attribution (link you/and repo in the code and release) and ping you if I get it to work. Maybe I end up delegating this to your library but making it compatible with our app will make it incompatible with ferdium and 99% of electron apps. Looking forward to see this integrated in this ferdium-app. Take care and thanks for contributing to the community. FYI: Goes without saying that if I find any improvements that can be shared for your library/repo, I will create a PR. |
|
Sounds good @IsmaelMartinez, feel free to use whatever is useful. I only worked on this because I want to fix passkeys in Ferdium — that's the goal. Happy to help if you have questions about the approach. |
Summary
Page.addScriptToEvaluateOnNewDocument) into both service webviews and popup BrowserWindowsRuntime.addBindingas an IPC bridge for popup windows that lack preload scripts (e.g. Google passkey settings page)navigator.userAgentData.brandsto include "Google Chrome" so Google allows passkey creationContext
Electron on Linux has no WebAuthn support —
navigator.credentials.create/getsilently fails because Electron'sWebAuthenticationDelegateis a stub. This blocks passkey login flows for services like Google, Microsoft, GitHub, etc.The
electron-webauthn-linuxpackage (separate repo) provides:passkey-rsRust crates (compiled to native Node addon via napi-rs)Architecture
This PR wires it into Ferdium with three integration points:
src/index.ts): callssetupWebAuthn()afterapp.on('ready'), then uses CDP to inject the page script into everywebContents(webviews AND popup windows)src/webview/recipe.ts): exposeswindow.electronWebAuthnIPC bridge viacontextBridgefor the standard pathRuntime.addBinding('__webauthnBridge')since popups don't have the recipe preload. The page script detects this and falls back to the CDP bridge.The page script monkey-patches:
navigator.credentials.create/getto route through IPC to the native Rust authenticatorPublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailableto returntruenavigator.userAgentData.brandsto include "Google Chrome" (required by Google for passkey creation)All WebAuthn code is gated behind
isLinux— no impact on macOS/Windows.The dependency
electron-webauthn-linux@0.1.0is published on npm.Test plan
pnpm typecheckpassespnpm lintpasses (zero warnings)pnpm testpassespnpm prepare-codepasses via pre-commit hook🤖 Generated with Claude Code